/*
	Copyright (c) 2008, Grant Jones <grant@podtronics.com>.
	All rights reserved.

	Redistribution and use in source and binary forms, with or
	without modification, are permitted provided that the following
	conditions are met:

		Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
		
		Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in
		the documentation and/or other materials provided with the
		distribution.
		
		Neither the name of the gxjones.com nor the names of its
		contributors may be used to endorse or promote products derived
		from this software without specific prior written permission.
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
	FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
	OF THE POSSIBILITY OF SUCH DAMAGE.
	
	http://www.opensource.org/licenses/bsd-license.php
*/

/*
 *  edb_domain_insert.c
 *  scatterd
 *
 *  Created by Grant Jones on 3/5/08.
 *
 */
#ifdef INSERT_LIBUUID
	/* include before the system includes (it has a workaround for uuid_t conflicts) */
	#include "uuid.h"
#endif

#include "edb_main.h"
#include "edb_msg_parser.h"
#include "edb_msg_utils.h"
#include "strmap.h"
#include "edb_domain.h"
#include "edb_domain_insert.h"
#include "edb_domain_select.h"
#include "edb_domain_index.h"

#include <ctype.h>


#ifdef INSERT_AUTO_INCREMENT	
int edb_domain_next_uid( edb_domain_ref_t *dref, unsigned long *result_uid )
{
#define NAME_NEXT_UID_STR		"_name.next.uid"
#define NAME_NEXT_UID_STR_SZ	14
	off_t tag_loc;
	char uidstr[64];
	rtvalue_t data = { 64, uidstr };
	rtvalue_t key = { NAME_NEXT_UID_STR_SZ, NAME_NEXT_UID_STR };
	rtree_t *rt_db_ptr = dref->d->rt_db_ptr;
	if( !dref || !dref->d )
		return -1;
	memset( uidstr, 0, 64 );
	

	if( (tag_loc = rt_root_get( rt_db_ptr, &key, &data )) == 0ULL )
	{
		uidstr[0] = '1';
		uidstr[1] = '\0';
		*result_uid = 0;
		return rt_root_set( rt_db_ptr, &key, &data, &tag_loc);
	}

	if( !isdigit(uidstr[0]) )
	{
		edb_log_printf(0, "edb_domain_next_uid : existing uid is non-numeric \"%s\"\n", uidstr );
		return -1;
	}
	
/*	edb_log_printf(0, "edb_domain_next_uid : previous uid was: \"%s\"\n", uidstr );*/
	uidstr[63] = '\0';
	*result_uid = strtol(uidstr, NULL, 10);
	
	snprintf(uidstr, 63, "%ld", (*result_uid)+1 );
	
/*	edb_log_printf(0, "edb_domain_next_uid : write \"%s\"\n", uidstr );*/
	
	return rt_root_set( rt_db_ptr, &key, &data, &tag_loc);
}
#endif

/*!
	@function edb_domain_insert_uncond
	@abstract Inserts an item unconditionally into the domain dref
	@discussion 
	@param pcs the program context
	@param dref 
	@param insert_values
	@param result_items the result of the operation is appended to this list
	@result 0 on success; != 0 on error
*/
int edb_domain_insert_uncond( edb_domain_ref_t *dref, AttrList *insert_values, cmapStruct *result_items )
{
	int err_code = 0;
	rtvalue_t key, data;
	char *uidstr = NULL;
	/* allocate result spaces for 2 attributes: result, error or result, _name */
	edb_net_item_t *ri = edb_add_result( result_items, 2 );	
	if(!ri)
		return -EDB_ERR_ALLOCATE;	
	
	if( edb_domain_begin( dref ) >= 0 )
	{
//		void *strmap_lookup( cmapStruct *cmap, const char *key, size_t key_sz )
		AttrValue *name_val_ptr = strmap_lookup( insert_values, EDB_NAME_KEY, strlen(EDB_NAME_KEY) );
		
		/* use the root _name index: */
		rtree_use( dref->d->rt_db_ptr, NULL, 0 );			/* position list cursor at root */
		rtree_use( dref->d->rt_db_ptr, dref->d->dname, dref->d->dname_sz );		
		rtree_use( dref->d->rt_db_ptr, EDB_NAME_KEY, strlen(EDB_NAME_KEY) );		
				
		if( !name_val_ptr )
		{
			/* obtain a new _name value (next UID) */
#ifdef INSERT_AUTO_INCREMENT			
//			char uidstr[64];
			unsigned long item_uid = 0;
			uidstr = (char *)malloc( 64 );
			if(edb_domain_next_uid(dref, &item_uid))
			{
				err_code = EDB_ERR_DOMAIN_NEXT_UID;
				goto cleanup;
			}
			
			/* 0        1         2  */
			/* 123456789012345678901 */
			/* 18446744073709551615  */
			
			/* lld would be long long, item_uid is only long right now */
			sprintf( uidstr, "%.20ld", item_uid );
#elif INSERT_LIBUUID
			uuid_t *uuid;
			
			uuid_create(&uuid);
			uuid_make(uuid, UUID_MAKE_V1);
			uidstr = NULL;
			uuid_export(uuid, UUID_FMT_STR, (void **)&uidstr, NULL);
			uuid_destroy(uuid);
#else
			#error An auto ID generator must be defined 
#endif			

#ifdef FOO
			edb_log_printf(0, "new item uid string: \"%s\"", uidstr );
#endif
			Token name_key;
			Token name_value;
			
			name_key.z = (unsigned char *)EDB_NAME_KEY;
			name_key.n = strlen(EDB_NAME_KEY);
			
			name_value.z = (unsigned char *)uidstr;
			name_value.n = 20;

			/* allocate a new attribute (the _name attribute) and add it to the insert values: */
			edbAppendAttrListFromPool( result_items->mempool, insert_values, edbCreateAttrValueFromPool(result_items->mempool, &name_key, &name_value ) );
			
			name_val_ptr = strmap_lookup( insert_values, EDB_NAME_KEY, strlen(EDB_NAME_KEY) );
		}
/* 		
		else
		{
			_name already has a value, use it
		}
*/		
		edb_domain_construct_item( &data, insert_values );
		key.data = (unsigned char *)name_val_ptr->value.z;
		key.size = name_val_ptr->value.n;
		edb_log_printf(0, "insert key: ");
		edb_log_dump(0, key.data, key.size);
		edb_log_printf(0,"\n");

		if( rt_put( dref->d->rt_db_ptr, &key, &data ) )
		{
			err_code = EDB_ERR_DOMAIN_PUT_FAIL;
			goto cleanup;
		}

		if( uidstr )
		{
			free(uidstr);
			uidstr = NULL;
		}

		/* here we update our sync table, only if we aren't inserting into the sync table */
		edb_domain_notify_sync( dref, &key );
		
		/* apply indexing: */
		edb_domain_index_item( dref, insert_values, &key );
		
		err_code = edb_domain_end( dref );	
	}
	
	
	
cleanup:

	if( err_code == 0  )
	{
		edb_term_result_attr( result_items->mempool, &ri->attrs[0], EDB_RESULT_KEY, EDB_RESULT_VALUE_OK );

		edb_malloc_result_attr( result_items->mempool,  &ri->attrs[1], EDB_NAME_KEY, strlen(EDB_NAME_KEY), key.size );
		memcpy( ri->attrs[1].value, key.data, key.size );


		return 0;
	}
	else
	{
		edb_term_result_attr( result_items->mempool, &ri->attrs[0], EDB_RESULT_KEY, EDB_RESULT_VALUE_FAILURE );
		edb_term_result_attr( result_items->mempool, &ri->attrs[1], EDB_ERROR_KEY, "%d", err_code );
	}
	return -err_code;
}



/*!
	@function edb_domain_insert_uncond
	@abstract Inserts an item unconditionally into 
	@discussion Releases a domain reference obtained via edb_use_domain
	@param pcs the program context
	@param dref the domain reference to release
	@param result_items the result of the operation is appended to this list
	@result 0 on success; != 0 on error
*/
int edb_domain_insert( edb_domain_ref_t *dref, AttrList *insert_values, Expr *where_expr, cmapStruct *result_items )
{
	int err_code = 0;
	rtvalue_t key, data;
	int affect_count = 0;
	/* allocate result spaces for 3 attributes: result, error, affect_count or result, _name, affect_count */
	edb_net_item_t *ri = edb_add_result( result_items, 3 );	
	if(!ri)
		return -EDB_ERR_ALLOCATE;	
	
	

	cmapStruct *s_result = cmap_new(result_items->mempool);
	cmapEnumStruct attr_items_e;
	CMAP_KEY_TYPE item_num;
	AttrList *attrs_out;
	edb_domain_select_base( dref, NULL, where_expr, NULL, NULL, NULL, NULL, s_result );
	
	
	
	if( edb_domain_begin( dref ) >= 0 )
	{
//		void *strmap_lookup( cmapStruct *cmap, const char *key, size_t key_sz )




		cmap_enum(s_result, &attr_items_e);
			
		while( (attrs_out = cmap_enum_next(&attr_items_e, &item_num)) )
		{	
			strmapEnumStruct e;
			char *attr_key;
			size_t attr_key_sz;
			AttrValue *replace_val_ptr ;
			
			strmap_enum(insert_values, &e);
			
			/* merge attrs_out with insert_values */
			while( (replace_val_ptr = strmap_enum_next(&e, &attr_key, &attr_key_sz)) )
				strmap_assign(attrs_out, attr_key, attr_key_sz, replace_val_ptr );
				
			AttrValue *name_val_ptr = strmap_lookup( attrs_out, EDB_NAME_KEY, strlen(EDB_NAME_KEY) );

			edb_domain_construct_item( &data, attrs_out );
			key.data = (unsigned char *)name_val_ptr->value.z;
			key.size = name_val_ptr->value.n;

			/* use the root _name index: */
			rtree_use( dref->d->rt_db_ptr, NULL, 0 );			/* position list cursor at root */	
			rtree_use( dref->d->rt_db_ptr, dref->d->dname, dref->d->dname_sz );					
			rtree_use( dref->d->rt_db_ptr, EDB_NAME_KEY, strlen(EDB_NAME_KEY) );
					
			if( rt_put( dref->d->rt_db_ptr, &key, &data ) )
			{
				err_code = EDB_ERR_DOMAIN_PUT_FAIL;
				goto cleanup;
			}

			/* here we update our sync table, only if we aren't inserting into the sync table */
			edb_domain_notify_sync( dref, &key );
			
			/* apply indexing: */
			edb_domain_index_item( dref, insert_values, &key );

			affect_count++;
		}
		
		err_code = edb_domain_end( dref );	
	}
	
	
	
cleanup:

	if( err_code == 0  )
	{
		ri->attr_count = 0;
		
		edb_term_result_attr( result_items->mempool, &ri->attrs[ri->attr_count], EDB_RESULT_KEY, EDB_RESULT_VALUE_OK );
		ri->attr_count ++;
		
		edb_malloc_result_attr( result_items->mempool,  &ri->attrs[ri->attr_count], EDB_NAME_KEY, strlen(EDB_NAME_KEY), key.size );
		memcpy( ri->attrs[ri->attr_count].value, key.data, key.size );
		ri->attr_count ++;

		edb_term_result_attr( result_items->mempool, &ri->attrs[ri->attr_count], EDB_AFFECT_COUNT_KEY, "%d", affect_count);
		ri->attr_count ++;


		return 0;
	}
	else
	{
		ri->attr_count = 0;
		
		edb_term_result_attr( result_items->mempool, &ri->attrs[ri->attr_count], EDB_RESULT_KEY, EDB_RESULT_VALUE_FAILURE );
		ri->attr_count ++;
		
		edb_term_result_attr( result_items->mempool, &ri->attrs[ri->attr_count], EDB_ERROR_KEY, "%d", err_code );
		ri->attr_count ++;
		
		edb_term_result_attr( result_items->mempool, &ri->attrs[ri->attr_count], EDB_AFFECT_COUNT_KEY, "%d", affect_count);
		ri->attr_count ++;
		
	}
	return -err_code;
}



